﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;
using Vk.Methods.Photos;
using Vk.Methods.Wall;

#endregion

namespace Vk.Methods.Video
{
    /// <summary>
    ///     Видеозаписи
    /// </summary>
    public class Videos
    {
        private readonly Api _api;

        public Videos(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Копирует видеозапись на страницу пользователя.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи. </param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит видеозапись.</param>
        /// <param name="targetId">Идентификатор пользователя или сообщества, в которое нужно добавить видео. </param>
        /// <returns>При успешном копировании видеозаписи сервер вернет идентификатор созданной видеозаписи (vid). </returns>
        public async Task<int> Add(int videoId, int ownerId, int? targetId = null)
        {
            var query = new ApiQuery("video.add")
                .Add("video_id", videoId)
                .Add("owner_id", ownerId)
                .Add("target_id", targetId);
            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Создает пустой альбом видеозаписей.
        /// </summary>
        /// <param name="title">Название альбома.</param>
        /// <param name="groupId">
        ///     ID  группы, которой принадлежат видеозаписи. Если параметр не указан, то альбом создается у текущего
        ///     пользователя.
        /// </param>
        /// <param name="privacy">Настройки доступа к альбому </param>
        /// <returns>Возвращает album_id - id созданного альбома. </returns>
        public async Task<int> AddAlbum(string title, int? groupId, Privacy privacy)
        {
            var query = new ApiQuery("video.addAlbum")
                .Add("title", title)
                .Add("privacy", privacy)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);

            var id = result.Int("album_id");
            return _api.Manager.MethodSuccessed && id.HasValue ? id.Value : -1;
        }

        /// <summary>
        ///     Позволяет добавить видеозапись в альбом.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи. </param>
        /// <param name="ownerId">Идентификатор владельца видеозаписи. </param>
        /// <param name="targetId">Идентификатор владельца альбома, в который нужно добавить видео. </param>
        /// <param name="albumId">
        ///     Идентификатор альбома, в который нужно добавить видео.
        ///     Для добавления видео в общий альбом «Добавленные» передавайте -2.
        /// </param>
        /// <param name="albumIds">Идентификаторы альбомов, в которые нужно добавить видео. </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> AddToAlbum(int videoId,
                                           int ownerId,
                                           int? targetId = null,
                                           int? albumId = null,
                                           IEnumerable<int> albumIds = null)
        {
            var query = new ApiQuery("video.addToAlbum")
                .Add("video_id", videoId)
                .Add("owner_id", ownerId)
                .Add("album_id", albumId)
                .Add("album_ids", albumIds)
                .Add("target_id", targetId);
            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Cоздает новый комментарий к видеозаписи.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи.</param>
        /// <param name="attachments">Список объектов, приложенных к комментарию</param>
        /// <param name="fromGroup">
        ///     Данный параметр учитывается, если oid меньше 0 (комментарий к видеозаписи группы). true —
        ///     комментарий будет опубликован от имени группы, false — комментарий будет опубликован от имени пользователя (по
        ///     умолчанию).
        /// </param>
        /// <param name="replyToComment"></param>
        /// <param name="message">Текст комментария (минимальная длина - 2 символа).</param>
        /// <param name="ownerId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <param name="stickerId">Идентификатор стикера.</param>
        /// <param name="guid">
        ///     Уникальный идентификатор, предназначенный для предотвращения повторной отправки одинакового
        ///     комментария.
        /// </param>
        /// <returns>Возвращает идентификатор созданного комментария (cid) или код ошибки. </returns>
        public async Task<int> CreateComment(int videoId,
                                             string message = null,
                                             int? ownerId = null,
                                             IEnumerable<WallAttachment> attachments = null,
                                             bool? fromGroup = null,
                                             int? replyToComment = null,
                                             int? stickerId = null,
                                             string guid = null)
        {
            if (message == null & attachments == null)
                throw new Exception("At least on of the parameters must be not null: message, attachments");

            var query = new ApiQuery("video.createComment")
                .Add("video_id", videoId)
                .Add("message", message)
                .Add("attachments", attachments)
                .Add("owner_id", ownerId)
                .Add("from_group", fromGroup)
                .Add("sticker_id", stickerId)
                .Add("guid", guid)
                .Add("reply_to_comment", replyToComment);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Удаляет видеозапись со страницы пользователя.
        /// </summary>
        /// <param name="videoId">ID видеозаписи.</param>
        /// <param name="ownerId">ID владельца видеозаписи.</param>
        /// <param name="targetId">Идентификатор пользователя или сообщества, для которого нужно удалить видеозапись. </param>
        /// <returns>При успешном удалении видеозаписи сервер вернет true. </returns>
        public async Task<bool> Delete(int videoId, int ownerId, int? targetId = null)
        {
            var query = new ApiQuery("video.delete")
                .Add("video_id", videoId)
                .Add("target_id", targetId)
                .Add("owner_id", ownerId);
            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Удаляет альбом видеозаписей.
        /// </summary>
        /// <param name="albumId">ID удаляемого альбома.</param>
        /// <param name="groupId">
        ///     ID группы, которой принадлежат видеозаписи. Если параметр не указан, то альбом удаляется у текущего
        ///     пользователя.
        /// </param>
        /// <returns>Возвращает true в случае успеха. </returns>
        public async Task<bool> DeleteAlbum(int albumId, int? groupId)
        {
            var query = new ApiQuery("video.deleteAlbum")
                .Add("album_id", albumId)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Удаляет комментарий к видеозаписи.
        /// </summary>
        /// <param name="commentId">Идентификатор комментария.</param>
        /// <param name="ownerId">Идентификатор пользователя (по-умолчанию - текущий пользователь).</param>
        /// <returns>Возвращает true в случае успешного удаления. </returns>
        public async Task<bool> DeleteComment(int commentId, int? ownerId = null)
        {
            var query = new ApiQuery("video.deleteComment")
                .Add("comment_id", commentId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует данные видеозаписи на странице пользователя.
        /// </summary>
        /// <param name="videoId">ID видеозаписи.</param>
        /// <param name="ownerId">ID владельца видеозаписи.</param>
        /// <param name="name">Название видеозаписи.</param>
        /// <param name="desc">Описание видеозаписи.</param>
        /// <param name="privacyView">Приватность на просмотр видео в соответствии с форматом приватности.</param>
        /// <param name="privacyComment">Приватность на просмотр видео в соответствии с форматом приватности.</param>
        /// <param name="repeat">Зацикливание воспроизведения видеозаписи</param>
        /// <param name="noComments">Закрыть комментарии (для видео из сообществ).</param>
        /// <returns>При успешном редактировании видеозаписи вернет true. </returns>
        public async Task<bool> Edit(int videoId,
                                     int ownerId,
                                     string name = null,
                                     string desc = null,
                                     Privacy privacyView = null,
                                     Privacy privacyComment = null,
                                     bool? repeat = null,
                                     bool? noComments = null)
        {
            var query = new ApiQuery("video.edit")
                .Add("video_id", videoId)
                .Add("owner_id", ownerId)
                .Add("name", name)
                .Add("desc", desc)
                .Add("privacy_comment", privacyComment)
                .Add("privacy_view", privacyView)
                .Add("repeat", repeat)
                .Add("no_comments", noComments);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует название альбома видеозаписей.
        /// </summary>
        /// <param name="title">Новое название альбома.</param>
        /// <param name="albumId">ID редактируемого альбома.</param>
        /// <param name="groupId">
        ///     ID группы, которой принадлежат видеозаписи. Если параметр не указан, то изменяется альбом текущего
        ///     пользователя.
        /// </param>
        /// <returns>Возвращает true в случае успеха. </returns>
        public async Task<bool> EditAlbum(string title, int albumId, int? groupId = null, Privacy privacy = null)
        {
            var query = new ApiQuery("video.editAlbum")
                .Add("album_id", albumId)
                .Add("title", title)
                .Add("privacy", privacy)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Изменяет текст комментария к видеозаписи.
        /// </summary>
        /// <param name="commentId">Идентификатор комментария.</param>
        /// <param name="message">Текст комментария (минимальная длина - 2 символа).</param>
        /// <param name="attachments">Новый список объектов, приложенных к комментарию</param>
        /// <param name="ownerId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <returns>Возвращает true в случае успешного изменения. </returns>
        public async Task<bool> EditComment(int commentId,
                                            string message = null,
                                            IEnumerable<WallAttachment> attachments = null,
                                            int? ownerId = null)
        {
            if (message == null & attachments == null)
                throw new Exception("Должен быть указан хоть один параметр: message, attachments");

            var query = new ApiQuery("video.editComment")
                .Add("comment_id", commentId)
                .Add("message", message)
                .Add("attachments", attachments)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Возвращает информацию о видеозаписях.
        /// </summary>
        /// <param name="videos">
        ///     Перечисленные через запятую идентификаторы – идущие через знак подчеркивания id пользователей,
        ///     которым принадлежат видеозаписи, и id самих видеозаписей. Если видеозапись принадлежит группе, то в качестве
        ///     первого параметра используется -id группы.
        /// </param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежат видеозаписи</param>
        /// <param name="extended">Определяет, возвращать ли информацию о настройках приватности видео для текущего пользователя. </param>
        /// <param name="albumId">ID альбома видеозаписи из которого нужно вернуть.</param>
        /// <param name="width">
        ///     Требуемая ширина изображений видеозаписей в пикселах. Возможные значения - 130, 160 (по умолчанию),
        ///     320.
        /// </param>
        /// <param name="count">Количество возвращаемых видеозаписей (максимум 200).</param>
        /// <param name="offset">Смещение относительно первой найденной видеозаписи для выборки определенного подмножества.</param>
        public async Task<ListCount<Video>> Get(IEnumerable<string> videos = null,
                                                int? ownerId = null,
                                                bool? extended = null,
                                                int? albumId = null,
                                                int? width = null,
                                                int? count = null,
                                                int? offset = null)
        {
            if (count > 200)
                throw new ArgumentException("Параметр 'count' должен быть < 200");

            var query = new ApiQuery("video.get")
                .Add("videos", videos)
                .Add("owner_id", ownerId)
                .Add("album_id", albumId)
                .Add("width", width)
                .Add("count", count)
                .Add("offset", offset)
                .Add("extended", extended);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                if (!result.ChildNodes.Any())
                {
                    return null;
                }
                var nodes = result.SelectNodes("items/*");
                return new ListCount<Video>(result.Int("count").Value, nodes.Select(node => new Video(node)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Позволяет получить информацию об альбоме с видео.
        /// </summary>
        /// <param name="albumId">Идентификатор альбома. </param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит альбом. </param>
        /// <returns></returns>
        public async Task<VideoAlbum> GetAlbumById(int albumId, int? ownerId = null)
        {
            var query = new ApiQuery("video.getAlbumById")
                .Add("owner_id", ownerId)
                .Add("album_id", albumId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed ? new VideoAlbum(result) : null;
        }

        /// <summary>
        ///     Возвращает список альбомов видеозаписей пользователя или группы.
        /// </summary>
        /// <param name="ownerId">
        ///     Идентификатор владельца альбомов (пользователь или сообщество). По умолчанию — идентификатор
        ///     текущего пользователя.
        /// </param>
        /// <param name="count">Количество альбомов, которое необходимо вернуть. (по умолчанию – не больше 50, максимум - 100).</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества альбомов.</param>
        /// <param name="extended">
        ///     true – позволяет получать поля count, photo_320 и photo_160 для каждого альбома. Если альбом
        ///     пустой то поля photo_320 и photo_160 возвращены не будут. По умолчанию – 0.
        /// </param>
        /// <param name="needSystem">true — возвращать системные альбомы. </param>
        public async Task<ListCount<VideoAlbum>> GetAlbums(int? ownerId = null,
                                                           int? count = null,
                                                           int? offset = null,
                                                           bool? extended = null,
                                                           bool? needSystem = null)
        {
            var query = new ApiQuery("video.getAlbums")
                .Add("owner_id", ownerId)
                .Add("count", count)
                .Add("offset", offset)
                .Add("extended", extended)
                .Add("need_system", needSystem);

            var result = await _api.Manager.Execute(query);

            var nodes = result.SelectNodes("items/*");
            return _api.Manager.MethodSuccessed && nodes != null && nodes.Any()
                       ? new ListCount<VideoAlbum>(result.Int("count").Value,
                                                   nodes.Select(x => new VideoAlbum(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список альбомов, в которых находится видеозапись.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи. </param>
        /// <param name="ownerId">Идентификатор владельца видеозаписи. </param>
        /// <param name="targetId">
        ///     Идентификатор пользователя или сообщества, для которого нужно получить список альбомов,
        ///     содержащих видеозапись.
        /// </param>
        /// <returns>Список идентификаторов альбомов</returns>
        public async Task<List<int>> GetAlbumsByVideo(int videoId, int ownerId, int? targetId = null)
        {
            var query = new ApiQuery("video.getAlbumsByVideo")
                .Add("video_id", videoId)
                .Add("owner_id", ownerId)
                .Add("target_id", targetId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed ? result.ChildNodes.Select(x => x.Int().Value).ToList() : null;
        }

        /// <summary>
        ///     Возвращает список альбомов, в которых находится видеозапись.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи. </param>
        /// <param name="ownerId">Идентификатор владельца видеозаписи. </param>
        /// <param name="targetId">
        ///     Идентификатор пользователя или сообщества, для которого нужно получить список альбомов,
        ///     содержащих видеозапись.
        /// </param>
        /// <returns>Список альбомов</returns>
        public async Task<List<VideoAlbum>> GetAlbumsByVideoExtended(int videoId, int ownerId, int? targetId = null)
        {
            var query = new ApiQuery("video.getAlbumsByVideo")
                .Add("video_id", videoId)
                .Add("owner_id", ownerId)
                .Add("target_id", targetId)
                .Add("extended", true);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed ? result.ChildNodes.Select(x => new VideoAlbum(x)).ToList() : null;
        }

        /// <summary>
        ///     Позволяет получить представление каталога видео.
        /// </summary>
        /// <param name="count">
        ///     Число блоков каталога, информацию о которых необходимо вернуть. Обратите внимание, параметр
        ///     распространяется только на блоки other.
        /// </param>
        /// <param name="itemsCount">Число видеозаписей в каждом блоке. </param>
        /// <param name="from">
        ///     Параметр для получения следующей страницы результатов. Значение, необходимое для передачи в этом
        ///     параметре, возвращается в поле ответа next.
        /// </param>
        /// <param name="extended">
        ///     true — в ответе будут возвращены дополнительные поля profiles и groups, содержащие информацию о
        ///     пользователях и сообществах. По умолчанию: false.
        /// </param>
        /// <param name="filters">
        ///     id блоков, которые необходимо вернуть в ответе. Может содержать значения:
        ///     feed — видео из ленты новостей пользователя;
        ///     ugc — популярное;
        ///     top — выбор редакции;
        ///     series — сериалы и телешоу;
        ///     other — прочие блоки.
        /// </param>
        /// <param name="grouped">true — элементы каталога в ответе будут сгруппированы в объекте blocks. </param>
        public async Task<ItemsCountProfilesGroups<VideoCatalogBlock>> GetCatalog(int? count = null,
                                                                                  int? itemsCount = null,
                                                                                  string from = null,
                                                                                  bool? extended = null,
                                                                                  IEnumerable<string> filters = null,
                                                                                  bool? grouped = null)
        {
            //TODO: "next" field
            var apiQuery = new ApiQuery("video.getCatalog", true)
                .Add("count", count)
                .Add("items_count", itemsCount)
                .Add("from", from)
                .Add("extended", extended)
                .Add("grouped", grouped)
                .Add("filters", filters.ToString());

            var result = await _api.Manager.Execute(apiQuery);

            return _api.Manager.MethodSuccessed
                       ? new ItemsCountProfilesGroups<VideoCatalogBlock>(result, node => new VideoCatalogBlock(node))
                       : null;
        }

        /// <summary>
        ///     Позволяет получить отдельный блок видеокаталога.
        /// </summary>
        /// <param name="sectionId">Значение id, полученное с блоком в методе video.getCatalog. </param>
        /// <param name="from">Значение next, полученное с блоком в методе video.getCatalog </param>
        /// <param name="extended">
        ///     true — в ответе будут возвращены дополнительные поля profiles и groups, содержащие информацию о
        ///     пользователях и сообществах. По умолчанию: false.
        /// </param>
        /// <param name="count">Число элементов блока, которое нужно вернуть. </param>
        /// <returns></returns>
        public async Task<ItemsCountProfilesGroups<VideoCatalogItem>> GetCatalogSection(int sectionId,
                                                                                        string from,
                                                                                        bool? extended = null,
                                                                                        int? count = null)
        {
            if (@from == null)
                throw new ArgumentNullException(nameof(@from));


            //TODO: "next" field
            var apiQuery = new ApiQuery("video.getCatalogSection", true)
                .Add("count", count)
                .Add("from", from)
                .Add("extended", extended)
                .Add("section_id", sectionId);

            var result = await _api.Manager.Execute(apiQuery);

            return _api.Manager.MethodSuccessed
                       ? new ItemsCountProfilesGroups<VideoCatalogItem>(result, node => new VideoCatalogItem(node))
                       : null;
        }

        /// <summary>
        ///     Возвращает список комментариев к видеозаписи.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи.</param>
        /// <param name="ownerId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <param name="needLikes">true — будет возвращено дополнительное поле likes. По умолчанию поле likes не возвращается. </param>
        /// <param name="count">Количество комментариев, которое необходимо получить (но не более 100).</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества комментариев.</param>
        /// <param name="sort">Порядок сортировки комментариев (asc - от старых к новым, desc - от новых к старым)</param>
        /// <param name="startCommentId">Идентификатор комментария, начиная с которого нужно вернуть список</param>
        /// <param name="extended">
        ///     true — в ответе будут возвращены дополнительные поля profiles и groups, содержащие информацию о
        ///     пользователях и сообществах. По умолчанию: false.
        /// </param>
        public async Task<ItemsCountProfilesGroups<Comment>> GetComments(int videoId,
                                                                         int? ownerId = null,
                                                                         bool? needLikes = null,
                                                                         int? count = null,
                                                                         int? offset = null,
                                                                         SortOrder sort = null,
                                                                         int? startCommentId = null,
                                                                         bool? extended = null)
        {
            var query = new ApiQuery("video.getComments")
                .Add("video_id", videoId)
                .Add("need_likes", needLikes)
                .Add("count", count)
                .Add("offset", offset)
                .Add("owner_id", ownerId)
                .Add("start_comment_id", startCommentId)
                .Add("extended", extended)
                .Add("sort", sort?.StringValue);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? new ItemsCountProfilesGroups<Comment>(result, node => new Comment(node))
                       : null;
        }

        /// <summary>
        ///     Возвращает список видеозаписей на которых есть непросмотренные отметки.
        /// </summary>
        /// <param name="offset">Смещение необходимой для получения определённого подмножества видеозаписей.</param>
        /// <param name="count">Количество видеозаписей которые необходимо вернуть.</param>
        public async Task<ListCount<TaggedVideo>> GetNewTags(int? offset = null, int? count = null)
        {
            var query = new ApiQuery("video.getNewTags")
                .Add("offset", offset)
                .Add("count", count);

            var result = await _api.Manager.Execute(query);

            var nodes = result.SelectNodes("items/*");
            return _api.Manager.MethodSuccessed && nodes != null && nodes.Any()
                       ? new ListCount<TaggedVideo>(result.Int("count").Value,
                                                    nodes.Select(x => new TaggedVideo(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список отметок на видеозаписи.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи.</param>
        /// <param name="ownerId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        public async Task<List<VideoTag>> GetTags(int videoId, int? ownerId)
        {
            var query = new ApiQuery("video.getTags")
                .Add("video_id", videoId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed ? result.ChildNodes.Select(x => new VideoTag(x)).ToList() : null;
        }

        /// <summary>
        ///     Возвращает список видеозаписей, на которых отмечен пользователь.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <param name="count">Количество видеозаписей, которое необходимо получить (но не более 100).</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества видеозаписей.</param>
        /// <param name="extended">
        ///     true — в ответе будут возвращены дополнительные поля profiles и groups, содержащие информацию о
        ///     пользователях и сообществах. По умолчанию: false.
        /// </param>
        public async Task<ItemsCountProfilesGroups<Video>> GetUserVideos(int? userId,
                                                                         int? count = null,
                                                                         int? offset = null,
                                                                         bool? extended = null)
        {
            var query = new ApiQuery("video.getUserVideos")
                .Add("user_id", userId)
                .Add("count", count)
                .Add("extended", extended)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? new ItemsCountProfilesGroups<Video>(result, node => new Video(node))
                       : null;
        }

        /// <summary>
        ///     Скрывает для пользователя раздел видеокаталога.
        /// </summary>
        /// <param name="sectionId">Значение id, полученное с блоком, который необходимо скрыть, в методе video.getCatalog </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> HideCatalogSection(int sectionId)
        {
            var apiQuery = new ApiQuery("video.getCatalogSection", true)
                .Add("section_id", sectionId);

            var result = await _api.Manager.Execute(apiQuery);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Перемещает видеозаписи в альбом.
        /// </summary>
        /// <param name="videoIds">ID видеозаписей, перечисленные через запятую.</param>
        /// <param name="albumId">ID альбома, в который перемещаются видеозаписи.</param>
        /// <param name="groupId">
        ///     ID группы, которой принадлежат видеозаписи. Если параметр не указан, то работа ведется с альбомом
        ///     текущего пользователя.
        /// </param>
        /// <returns>Возвращает true в случае успеха. </returns>
        public async Task<bool> MoveToAlbum(IEnumerable<string> videoIds, int albumId, int? groupId)
        {
            var query = new ApiQuery("video.moveToAlbum")
                .Add("video_ids", videoIds)
                .Add("album_id", albumId)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);

            return result.Bool().Value;
        }

        /// <summary>
        ///     Добавляет отметку на видеозапись.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи.</param>
        /// <param name="userId">Идентификатор пользователя, которого нужно отметить на видеозаписи.</param>
        /// <param name="ownerId">Идентификатор владельца видеозаписи (по умолчанию - текущий пользователь).</param>
        /// <param name="taggedName">Текст отметки. </param>
        /// <returns> После успешного выполнения возвращает идентификатор созданной отметки. </returns>
        public async Task<int> PutTag(int videoId, int userId, int? ownerId, string taggedName = null)
        {
            var query = new ApiQuery("video.putTag")
                .Add("video_id", videoId)
                .Add("user_id", userId)
                .Add("tagged_name", taggedName)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Позволяет убрать видеозапись из альбома.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи. </param>
        /// <param name="ownerId">Идентификатор владельца видеозаписи.</param>
        /// <param name="targetId">Идентификатор владельца альбома.</param>
        /// <param name="albumId">Идентификатор альбома, из которого нужно убрать видео. </param>
        /// <param name="albumIds">Идентификаторы альбомов, из которых нужно убрать видео. </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> RemoveFromAlbum(int videoId,
                                                int ownerId,
                                                int? targetId = null,
                                                int? albumId = null,
                                                IEnumerable<int> albumIds = null)
        {
            var query = new ApiQuery("video.removeFromAlbum")
                .Add("video_id", videoId)
                .Add("target_id", targetId)
                .Add("album_id", albumId)
                .Add("album_ids", albumIds)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Удаляет отметку с видеозаписи.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи.</param>
        /// <param name="tagId">Идентификатор отметки, которую нужно удалить.</param>
        /// <param name="ownerId">Идентификатор владельца видеозаписи (по умолчанию - текущий пользователь).</param>
        /// <returns>Возвращает true в случае успешного удаления или код ошибки. </returns>
        public async Task<bool> RemoveTag(int videoId, int tagId, int? ownerId)
        {
            var query = new ApiQuery("video.removeTag")
                .Add("video_id", videoId)
                .Add("tag_id", tagId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет изменить порядок альбомов с видео.
        /// </summary>
        /// <param name="albumId">Идентификатор альбома, который нужно переместить.</param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит альбом.</param>
        /// <param name="before">Идентификатор альбома, перед которым нужно поместить текущий. </param>
        /// <param name="after">Идентификатор альбома, после которого нужно поместить текущий. </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> ReorderAlbums(int albumId, int? ownerId = null, int? before = null, int? after = null)
        {
            var query = new ApiQuery("video.reorderAlbums")
                .Add("album_id", albumId)
                .Add("before", before)
                .Add("after", after)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет переместить видеозапись в альбоме.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи, которую нужно переместить. </param>
        /// <param name="ownerId">Идентификатор альбома с видеозаписью, которую нужно переместить. </param>
        /// <param name="targetId">Идентификатор пользователя или сообщества, в чьем альбоме нужно переместить видео.</param>
        /// <param name="albumId">Идентификатор альбома с видеозаписью, которую нужно переместить. </param>
        /// <param name="beforeOwnerId">
        ///     Идентификатор владельца видеозаписи, перед которой следует поместить текущую (пользователь
        ///     или сообщество).
        /// </param>
        /// <param name="afterOwnerId">
        ///     Идентификатор владельца видеозаписи, после которой следует поместить текущую (пользователь
        ///     или сообщество).
        /// </param>
        /// <param name="beforeVideoId">Идентификатор видеозаписи, перед которой следует поместить текущую. </param>
        /// <param name="afterVideoId">Идентификатор видеозаписи, после которой следует поместить текущую. </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> ReorderVideos(int videoId,
                                              int ownerId,
                                              int? targetId = null,
                                              int? albumId = null,
                                              int? beforeOwnerId = null,
                                              int? afterOwnerId = null,
                                              int? beforeVideoId = null,
                                              int? afterVideoId = null)
        {
            var query = new ApiQuery("video.reorderVideos")
                .Add("album_id", albumId)
                .Add("video_id", videoId)
                .Add("target_id", targetId)
                .Add("before_owner_id", beforeOwnerId)
                .Add("before_video_id", beforeVideoId)
                .Add("after_owner_id", afterOwnerId)
                .Add("after_video_id", afterVideoId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет пожаловаться на видеозапись.
        /// </summary>
        /// <param name="videoId">Идентификатор видеозаписи</param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит видеозапись</param>
        /// <param name="reason">Тип жалобы</param>
        /// <param name="comment">Комментарий для жалобы</param>
        /// <param name="searchQuery">Поисковой запрос, если видеозапись была найдена через поиск</param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> Report(int videoId,
                                       int ownerId,
                                       ReportReason reason = null,
                                       string comment = null,
                                       string searchQuery = null)
        {
            var query = new ApiQuery("video.report")
                .Add("owner_id", ownerId)
                .Add("video_id", videoId)
                .Add("reason", reason.Value)
                .Add("comment", comment)
                .Add("search_query", searchQuery);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return result.Bool().Value;
            }
            return false;
        }

        /// <summary>
        ///     Позволяет пожаловаться на комментарий к видеозаписи.
        /// </summary>
        /// <param name="commentId">Идентификатор комментария. </param>
        /// <param name="ownerId">Идентификатор владельца видеозаписи к которой оставлен комментарий.</param>
        /// <param name="reason">Тип жалобы</param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> ReportComment(int commentId, int ownerId, ReportReason reason = null)
        {
            var query = new ApiQuery("video.reportComment")
                .Add("owner_id", ownerId)
                .Add("comment_id", commentId)
                .Add("reason", reason.Value);
            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return result.Bool().Value;
            }
            return false;
        }

        /// <summary>
        ///     Восстанавливает удаленную видеозапись
        /// </summary>
        /// <param name="videoId">ID видеозаписи.</param>
        /// <param name="ownerId">ID владельца видеозаписи.</param>
        /// <returns>При успешном восстановлении видеозаписи сервер вернет true. </returns>
        public async Task<bool> Restore(int videoId, int ownerId)
        {
            var query = new ApiQuery("video.restore")
                .Add("video_id", videoId)
                .Add("owner_id", ownerId);
            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Восстанавливает удаленный комментарий
        /// </summary>
        /// <param name="commentId">Идентификатор комментария.</param>
        /// <param name="ownerId">Идентификатор пользователя (по-умолчанию - текущий пользователь).</param>
        /// <returns>Возвращает true в случае успешного удаления. </returns>
        public async Task<bool> RestoreComment(int commentId, int? ownerId = null)
        {
            var query = new ApiQuery("video.restoreComment")
                .Add("comment_id", commentId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Возвращает адрес сервера (необходимый для загрузки) и данные видеозаписи.
        /// </summary>
        /// <param name="name">Название видеофайла.</param>
        /// <param name="description">Описание видеофайла.</param>
        /// <param name="groupId">
        ///     Группа, в которую будет сохранён видеофайл. По умолчанию видеофайл сохраняется на страницу
        ///     пользователя.
        /// </param>
        /// <param name="isPrivate">
        ///     Указывается 1 в случае последующей отправки видеозаписи личным сообщением. После загрузки с
        ///     этим параметром видеозапись не будет отображаться в списке видеозаписей пользователя и не будет доступна другим
        ///     пользователям по id. По умолчанию 0.
        /// </param>
        /// <param name="privacyView">Приватность на просмотр видео в соответствии с форматом приватности.</param>
        /// <param name="privacyComment">Приватность на комментирование видео в соответствии с форматом приватности.</param>
        /// <param name="albumId">Идентификатор альбома, в который будет загружен видео файл. </param>
        /// <param name="link">
        ///     url для встраивания видео с внешнего сайта, например, с youtube. В этом случае нужно вызвать полученный upload_url
        ///     не прикрепляя файл, достаточно просто обратиться по этому адресу.
        /// </param>
        /// <param name="repeat">Зацикливание воспроизведения видеозаписи</param>
        /// <param name="wallPost">Требуется ли после сохранения опубликовать запись с видео на стене</param>
        /// <param name="noComments">true — закрыть комментарии (для видео из сообществ). По умолчанию: false. </param>
        public async Task<VideoSaveResult> Save(string name = null,
                                                string description = null,
                                                int? groupId = null,
                                                int? albumId = null,
                                                string link = null,
                                                bool? isPrivate = null,
                                                bool? wallPost = null,
                                                Privacy privacyView = null,
                                                Privacy privacyComment = null,
                                                bool? repeat = null,
                                                bool? noComments = null)
        {
            var query = new ApiQuery("video.save")
                .Add("name", name)
                .Add("group_id", groupId)
                .Add("album_id", albumId)
                .Add("description", description)
                .Add("isPrivate", isPrivate)
                .Add("wallpost", wallPost)
                .Add("privacy_view", privacyView)
                .Add("privacy_comment", privacyComment)
                .Add("link", link)
                .Add("no_comments", noComments)
                .Add("repeat", repeat);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed ? new VideoSaveResult(result) : null;
        }

        /// <summary>
        ///     Возвращает список видеозаписей в соответствии с заданным критерием поиска.
        /// </summary>
        /// <param name="q">Строка поискового запроса. Например, The Beatles.</param>
        /// <param name="sort">Вид сортировки.</param>
        /// <param name="hd">Если не равен нулю, то поиск производится только по видеозаписям высокого качества.</param>
        /// <param name="count">Количество возвращаемых видеозаписей (максимум 200).</param>
        /// <param name="offset">Смещение относительно первой найденной видеозаписи для выборки определенного подмножества.</param>
        /// <param name="adult">Отключение опции "безопасный поиск".</param>
        /// <param name="filters">Список критериев, по которым требуется отфильтровать видео.</param>
        /// <param name="searchOwn">
        ///     true – искать по видеозаписям пользователя, false – не искать по видеозаписям пользователя (по
        ///     умолчанию).
        /// </param>
        /// <param name="extended">
        ///     true — в ответе будут возвращены дополнительные поля profiles и groups, содержащие информацию о
        ///     пользователях и сообществах. По умолчанию: 0.
        /// </param>
        /// <param name="shorter">Количество секунд, видеозаписи короче которого необходимо вернуть. </param>
        /// <param name="longer">Количество секунд, видеозаписи длиннее которого необходимо вернуть. </param>
        public async Task<ItemsCountProfilesGroups<Video>> Search(string q,
                                                                  ApiEnum<VideoSortOrder> sort = null,
                                                                  bool? hd = null,
                                                                  VideoFilters filters = null,
                                                                  bool? searchOwn = null,
                                                                  int? count = null,
                                                                  int? offset = null,
                                                                  bool? adult = null,
                                                                  bool? extended = null,
                                                                  int? shorter = null,
                                                                  int? longer = null)
        {
            var apiQuery = new ApiQuery("video.search")
                .Add("q", q)
                .Add("sort", sort)
                .Add("hd", hd)
                .Add("count", count)
                .Add("offset", offset)
                .Add("adult", adult)
                .Add("search_own", searchOwn)
                .Add("extended", extended)
                .Add("shorter", shorter)
                .Add("longer", longer)
                .Add("filters", filters.ToString());

            var result = await _api.Manager.Execute(apiQuery);

            var nodes = result.SelectNodes("items/*");
            return _api.Manager.MethodSuccessed && nodes != null && nodes.Any()
                       ? new ItemsCountProfilesGroups<Video>(result, node => new Video(node))
                       : null;
        }
    }
}